#pragma once

#include "Ray.hpp"
#include "device/Vector_dev.hpp"
#include "device/dev_utils.hpp"
#include "UnifiedMemManaged.hpp"

class Bounds3 : public UnifiedMemManaged
{
public:
    Vector3fDev pMin, pMax; // two points to specify the bounding box
    __device__ Bounds3()
    {
        double minNum = -102400.0;
        double maxNum = 102400.0;
        pMax = Vector3fDev(minNum, minNum, minNum);
        pMin = Vector3fDev(maxNum, maxNum, maxNum);
    }

    Bounds3(bool isFromHost)
    {
        assert(isFromHost);
        double minNum = -102400.0;
        double maxNum = 102400.0;
        pMax = Vector3fDev(minNum, minNum, minNum, true);
        pMin = Vector3fDev(maxNum, maxNum, maxNum, true);
    }

    __device__ Bounds3(const Vector3fDev p) : pMin(p), pMax(p) {}
    __device__ Bounds3(const Vector3fDev p1, const Vector3fDev p2)
    {
        pMin = Vector3fDev(fmin(p1.x, p2.x), fmin(p1.y, p2.y), fmin(p1.z, p2.z));
        pMax = Vector3fDev(fmax(p1.x, p2.x), fmax(p1.y, p2.y), fmax(p1.z, p2.z));
    }

    Bounds3(const Vector3fDev p1, const Vector3fDev p2, bool isFromHost)
    {
        assert(isFromHost);
        pMin = Vector3fDev(fmin(p1.x, p2.x), fmin(p1.y, p2.y), fmin(p1.z, p2.z), true);
        pMax = Vector3fDev(fmax(p1.x, p2.x), fmax(p1.y, p2.y), fmax(p1.z, p2.z), true);
    }

    __device__ Vector3fDev Diagonal() const { return pMax - pMin; }
    __device__ int maxExtent() const
    {
        Vector3fDev d = Diagonal();
        if (d.x > d.y && d.x > d.z)
            return 0;
        else if (d.y > d.z)
            return 1;
        else
            return 2;
    }

    __device__ double SurfaceArea() const
    {
        Vector3fDev d = Diagonal();
        return 2 * (d.x * d.y + d.x * d.z + d.y * d.z);
    }

    __device__ Vector3fDev Centroid() { return pMin * 0.5 + pMax * 0.5; }
    __device__ Bounds3 Intersect(const Bounds3 &b)
    {
        return Bounds3(Vector3fDev(fmax(pMin.x, b.pMin.x), fmax(pMin.y, b.pMin.y),
                                   fmax(pMin.z, b.pMin.z)),
                       Vector3fDev(fmin(pMax.x, b.pMax.x), fmin(pMax.y, b.pMax.y),
                                   fmin(pMax.z, b.pMax.z)));
    }

    __device__ Vector3fDev Offset(const Vector3fDev &p) const
    {
        Vector3fDev o = p - pMin;
        if (pMax.x > pMin.x)
            o.x /= pMax.x - pMin.x;
        if (pMax.y > pMin.y)
            o.y /= pMax.y - pMin.y;
        if (pMax.z > pMin.z)
            o.z /= pMax.z - pMin.z;
        return o;
    }

    __device__ bool Overlaps(const Bounds3 &b1, const Bounds3 &b2)
    {
        bool x = (b1.pMax.x >= b2.pMin.x) && (b1.pMin.x <= b2.pMax.x);
        bool y = (b1.pMax.y >= b2.pMin.y) && (b1.pMin.y <= b2.pMax.y);
        bool z = (b1.pMax.z >= b2.pMin.z) && (b1.pMin.z <= b2.pMax.z);
        return (x && y && z);
    }

    __device__ bool Inside(const Vector3fDev &p, const Bounds3 &b)
    {
        return (p.x >= b.pMin.x && p.x <= b.pMax.x && p.y >= b.pMin.y &&
                p.y <= b.pMax.y && p.z >= b.pMin.z && p.z <= b.pMax.z);
    }
    __device__ const Vector3fDev &operator[](int i) const
    {
        return (i == 0) ? pMin : pMax;
    }

    __device__ bool IntersectP(const Ray &ray, const Vector3fDev &invDir,
                               const bool dirIsNeg[3]) const
    {
        // invDir: ray direction(x,y,z), invDir=(1.0/x,1.0/y,1.0/z), use this because Multiply is faster that Division
        // dirIsNeg: ray direction(x,y,z), dirIsNeg=[int(x>0),int(y>0),int(z>0)], use this to simplify your logic
        // TODO test if ray bound intersects
        Vector3fDev tMin = (pMin - ray.origin) * invDir;
        Vector3fDev tMax = (pMax - ray.origin) * invDir;

        if (dirIsNeg[0])
        {
            dev_swap(tMin.x, tMax.x);
        }

        if (dirIsNeg[1])
        {
            dev_swap(tMin.y, tMax.y);
        }

        if (dirIsNeg[2])
        {
            dev_swap(tMin.z, tMax.z);
        }

        float tEnter = dev_max(tMin.x, dev_max(tMin.y, tMin.z));
        float tExit = dev_max(tMax.y, dev_max(tMax.y, tMax.z));
        if (tEnter <= tExit && tExit >= 0)
            return true;
        else
            return false;
    }
};

__device__ static Bounds3 Union(const Bounds3 &b1, const Bounds3 &b2)
{
    Bounds3 ret;
    ret.pMin = Vector3fDev::Min(b1.pMin, b2.pMin);
    ret.pMax = Vector3fDev::Max(b1.pMax, b2.pMax);
    return ret;
}

__device__ static Bounds3 Union(const Bounds3 &b, const Vector3fDev &p)
{
    Bounds3 ret;
    ret.pMin = Vector3fDev::Min(b.pMin, p);
    ret.pMax = Vector3fDev::Max(b.pMax, p);
    return ret;
}

static Bounds3 Union(const Bounds3 &b, const Vector3fDev &p, bool isFromHost)
{
    assert(isFromHost);
    Bounds3 ret(true);
    ret.pMin = Vector3fDev::Min(b.pMin, p, true);
    ret.pMax = Vector3fDev::Max(b.pMax, p, true);
    return ret;
}
